﻿namespace com.crownpack.common
{
    using System;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.IO;
    using System.Text;

    /// <summary>
    /// CommandLineBase
    /// During the Process method the following occurs:
    ///     Initialize() is executed
    ///     If help is requested (? /? -? as first argument) then display help 
    ///         Exit
    ///     ParseForKeywords(args) is executed
    ///     Commands are syntactically validated
    ///     Determine if any required arguments are missing 
    ///         Display missing 
    ///         Exit
    ///     InitializeDefaults() is executed
    ///     Validate() is executed
    ///     If errors then display error messages
    ///         Exit
    ///     Execute() is executed
    ///     Display any error messages
    ///     Display any completion messages
    /// </summary>
    public abstract class CommandLineBase
    {
        #region Fields

        public const int EXITCODE_ERROR = 1;
        public const int EXITCODE_SUCCESS = 0;

        string _CommandName;

        #endregion Fields

        #region Constructors

        public CommandLineBase( )
        {
            Arguments = new CommandArguments( );
            completionMessages = new List<string>( );
            errorMessages = new List<string>( );
            ExitCode = EXITCODE_SUCCESS;
        }

        #endregion Constructors

        #region Properties

        public string CommandName
        {
            get
            {
                if ( string.IsNullOrEmpty( _CommandName ) )
                {
                    string s = Environment.GetCommandLineArgs( )[ 0 ];
                    FileInfo fi = new FileInfo( s );
                    _CommandName = fi.Name.ToUpper( );
                }
                return _CommandName;
            }
            private set
            {
                _CommandName = value;
            }
        }

        /// <summary>
        /// Gets or sets the exit code of the process. The default value is zero (success). Generally anything greater than zero denotes an error condition.
        /// </summary>
        public int ExitCode
        {
            get;
            set;
        }

        CommandArguments Arguments
        {
            get;
            set;
        }

        List<string> completionMessages
        {
            get;
            set;
        }

        List<string> errorMessages
        {
            get;
            set;
        }

        #endregion Properties

        #region Methods

        /// <summary>
        /// Add argument to the command.
        /// </summary>
        /// <param name="keyword"></param>
        /// <param name="argument"></param>
        public void AddArgument( Enum keyword, CommandArgument argument )
        {
            Arguments.Add( keyword, argument );
        }

        public void AddArgument( Enum keyword, bool required, params string[ ] keywords )
        {
            Arguments.Add( keyword, new CommandArgument( required, true, true, keywords ) );
        }

        /// <summary>
        /// Used to flag the argument as previously set during this command processing.
        /// Adds the following as an error message: Command argument 'N' previously specified.
        /// </summary>
        /// <param name="arg"></param>
        public void AddArgumentAlreadySpecifiedErrorMessage( string arg )
        {
            AddErrorMessage( "Command argument '{0}' previously specified.", arg );
        }

        /// <summary>
        /// Adds the specified message as a completion message.
        /// </summary>
        /// <param name="message"></param>
        public void AddCompletionMessage( string message )
        {
            completionMessages.Add( message );
        }

        /// <summary>
        /// Adds the specified message as a completion message.
        /// </summary>
        /// <param name="format"></param>
        /// <param name="args"></param>
        public void AddCompletionMessage( string format, params object[ ] args )
        {
            AddCompletionMessage( string.Format( format, args ) );
        }

        /// <summary>
        /// Adds the specified message as an error message.
        /// </summary>
        /// <param name="message"></param>
        public void AddErrorMessage( string message )
        {
            errorMessages.Add( string.Format( "--> {0}", message ) );
        }

        /// <summary>
        /// Adds the specified message as an error message.
        /// </summary>
        /// <param name="format"></param>
        /// <param name="args"></param>
        public void AddErrorMessage( string format, params object[ ] args )
        {
            AddErrorMessage( string.Format( format, args ) );
        }

        /// <summary>
        /// Displays the command help.
        /// </summary>
        public virtual void DisplayHelp( )
        {
            Console.WriteLine( "No help defined." );
        }

        public void DisplayHelp( string help )
        {
            Console.WriteLine( help );
        }

        /// <summary>
        /// Displays the specified message as a status message.
        /// </summary>
        /// <param name="message"></param>
        public void DisplayStatusMessage( string message )
        {
            Console.Clear( );
            Console.WriteLine( message );
        }

        /// <summary>
        /// Displays the specified message as a status message.
        /// </summary>
        /// <param name="format"></param>
        /// <param name="args"></param>
        public void DisplayStatusMessage( string format, params object[ ] args )
        {
            DisplayStatusMessage( string.Format( format, args ) );
        }

        /// <summary>
        /// Executes the command line.
        /// </summary>
        public virtual void Execute( )
        {
            throw new NotImplementedException( );
        }

        /// <summary>
        /// Gets the specified command argument.
        /// </summary>
        /// <param name="keyword"></param>
        /// <returns></returns>
        public CommandArgument GetCommandArgument( Enum keyword )
        {
            return Arguments.Get( keyword );
        }

        /// <summary>
        /// Identify argument keywords and their characteristics.
        /// Example: AddArgument( Keyword.keyword, new CommandArgument( new string [ ] { "-k", "-keyword" }, true, true, true ) );
        /// </summary>
        public virtual void Initialize( )
        {
            throw new NotImplementedException( );
        }

        public virtual void InitializeDefaults( )
        {
            return;
        }

        /// <summary>
        /// Determines if the command line argument is the specified argument.
        /// </summary>
        /// <param name="arg"></param>
        /// <param name="argument"></param>
        /// <returns></returns>
        public bool IsArgument( string arg, CommandArgument argument )
        {
            bool result = false;

            foreach ( string s in argument.Keywords )
                if ( ( argument.UpperCaseSupported &&
                    arg.Trim( ).ToUpper( ).Equals( s.Trim( ).ToUpper( ) ) ) ||
                    ( argument.LowerCaseSupported &&
                    arg.Trim( ).ToLower( ).Equals( s.Trim( ).ToLower( ) ) ) )
                {
                    argument.Keyword = arg.Trim( );
                    argument.Specified = true;
                    result = true;
                    break;
                }

            return result;
        }

        /// <summary>
        /// Parses the inbound arguments for command line arguments.
        /// </summary>
        /// <param name="args"></param>
        public virtual void ParseForKeywords( string[ ] args )
        {
            throw new NotImplementedException( );
        }

        public void Process( string[ ] args )
        {
            Initialize( );

            // no arguments or help request argument passed
            if ( args == null ||
                args.Length <= 0 ||
                args[ 0 ].StartsWith( "?" ) ||
                args[ 0 ].StartsWith( "/?" ) ||
                args[ 0 ].StartsWith( "-?" ) )
                DisplayHelp( );
            else
            {
                ParseForKeywords( args );
                syntacticallyValidateCommands( );

                foreach ( CommandArgument argument in Arguments.Values( ) )
                    if ( argument.Required && !argument.OK )
                        AddErrorMessage( string.Format( "Missing {0}.", argument.KeywordsToString( ) ) );

                // if error encountered, display errors
                if ( errorMessages.Count > 0 )
                {
                    if ( ExitCode == EXITCODE_SUCCESS )
                        ExitCode = EXITCODE_ERROR;
                    displayErrors( );
                    DisplayHelp( );
                }
                else
                {
                    InitializeDefaults( );

                    if ( Validate( ) )
                    {
                        // no errors, process

                        Execute( );

                        if ( errorMessages.Count > 0 )
                        {
                            if ( ExitCode == EXITCODE_SUCCESS )
                                ExitCode = EXITCODE_ERROR;
                            displayErrors( );
                        }

                        if ( completionMessages.Count > 0 )
                            displayCompletion( );
                    }
                    else
                    {
                        if ( errorMessages.Count > 0 )
                        {
                            if ( ExitCode == EXITCODE_SUCCESS )
                                ExitCode = EXITCODE_ERROR;
                            displayErrors( );
                        }
                    }
                }
            }
        }

        public void SetCommandArgumentValue( Enum keyword, string value )
        {
            Arguments.Get( keyword ).Value = value;
        }

        public virtual bool Validate( )
        {
            return true;
        }

        public bool VerifyNameSyntax( string value, string keyword )
        {
            bool ok = false;

            if ( value != string.Empty )
            {
                try
                {
                    FileInfo fileInfo = new FileInfo( value );
                    fileInfo = null;
                    ok = true;
                }
                catch ( Exception e )
                {
                    AddErrorMessage( string.Format( "{0} {1}: {1}", keyword, value, e.Message ) );
                }
            }
            else
            {
                AddErrorMessage( string.Format( "{0} Missing value.", keyword ) );
            }

            return ok;
        }

        void displayCompletion( )
        {
            if ( completionMessages.Count > 0 )
            {
                Console.WriteLine( "\nThe following was processed successfully:\n" );
                foreach ( string s in completionMessages )
                    Console.WriteLine( s );
            }
        }

        void displayErrors( )
        {
            if ( errorMessages.Count > 0 )
            {
                Console.WriteLine( "\nThe following errors occurred:\n" );
                foreach ( string s in errorMessages )
                    Console.WriteLine( s );
            }
        }

        void syntacticallyValidateCommands( )
        {
            foreach ( CommandArgument argument in Arguments.Values( ) )
            {
                argument.OK = false;
                foreach ( string s in argument.Keywords )
                {
                    if ( argument.UpperCaseSupported )
                    {
                        argument.OK = argument.Keyword == s.ToUpper( );
                        if ( argument.OK )
                            break;
                    }

                    if ( argument.LowerCaseSupported )
                    {
                        argument.OK = argument.Keyword == s.ToLower( );
                        if ( argument.OK )
                            break;
                    }
                }
            }
        }

        #endregion Methods

        #region Nested Types

        public class CommandArgument
        {
            #region Fields

            bool _IsValueSet;
            string _Value;

            #endregion Fields

            #region Constructors

            public CommandArgument( bool required, bool upperCaseSupported, bool lowerCaseSupported, params string[ ] keywords )
                : this(keywords, required, upperCaseSupported, lowerCaseSupported)
            {
            }

            public CommandArgument( bool defaultValue, bool required, bool upperCaseSupported, bool lowerCaseSupported, params string[ ] keywords )
                : this(keywords, required, upperCaseSupported, lowerCaseSupported, defaultValue)
            {
            }

            public CommandArgument( string defaultValue, bool required, bool upperCaseSupported, bool lowerCaseSupported, params string[ ] keywords )
                : this(keywords, required, upperCaseSupported, lowerCaseSupported, defaultValue)
            {
            }

            CommandArgument( string[ ] keywords, bool required, bool upperCaseSupported, bool lowerCaseSupported )
            {
                Keywords = keywords;
                Required = required;
                UpperCaseSupported = upperCaseSupported;
                LowerCaseSupported = lowerCaseSupported;
            }

            CommandArgument( string[ ] keywords, bool required, bool upperCaseSupported, bool lowerCaseSupported, string defaultValue )
                : this(keywords, required, upperCaseSupported, lowerCaseSupported)
            {
                Value = defaultValue;
            }

            CommandArgument( string[ ] keywords, bool required, bool upperCaseSupported, bool lowerCaseSupported, bool defaultValue )
                : this(keywords, required, upperCaseSupported, lowerCaseSupported)
            {
                Value = defaultValue.ToString( );
            }

            CommandArgument( )
            {
            }

            #endregion Constructors

            #region Properties

            public bool IsValueSet
            {
                get
                {
                    return _IsValueSet;
                }
                private set
                {
                    _IsValueSet = value;
                }
            }

            public string Keyword
            {
                get;
                internal set;
            }

            public string[] Keywords
            {
                get;
                internal set;
            }

            public bool LowerCaseSupported
            {
                get;
                internal set;
            }

            public bool OK
            {
                get;
                internal set;
            }

            public bool Required
            {
                get;
                internal set;
            }

            public bool Specified
            {
                get;
                internal set;
            }

            public bool UpperCaseSupported
            {
                get;
                internal set;
            }

            public string Value
            {
                get
                {
                    return _Value;
                }
                set
                {
                    _Value = value;
                    _IsValueSet = true;
                }
            }

            #endregion Properties

            #region Methods

            public string KeywordsToString( )
            {
                StringBuilder sb = new StringBuilder( );
                foreach ( string s in Keywords )
                    sb.AppendFormat( "{0}\t", s.Trim( ) );

                string result = sb.ToString( ).Trim( '\t' ).Replace( "\t", " or " );

                sb = null;
                return result;
            }

            #endregion Methods
        }

        /// <summary>
        /// Command arguments
        /// </summary>
        public class CommandArguments : NameObjectCollectionBase
        {
            #region Methods

            public void Add( Enum key, CommandArgument value )
            {
                base.BaseAdd( key.ToString( ), value );
            }

            public CommandArgument Get( Enum key )
            {
                bool ok = false;
                for ( int i = 0; i < base.Keys.Count; i++ )
                    if ( base.Keys[ i ].Equals( key.ToString( ), StringComparison.OrdinalIgnoreCase ) )
                    {
                        ok = true;
                        break;
                    }

                return ok ? ( CommandArgument ) base.BaseGet( key.ToString( ) ) : null;
            }

            public CommandArgument[] Values( )
            {
                object[ ] values = base.BaseGetAllValues( );
                CommandArgument[ ] arguments = new CommandArgument[ values.Length ];
                int i = 0;
                foreach ( object o in values )
                {
                    arguments.SetValue( ( CommandArgument ) o, i );
                    i++;
                }
                return arguments;
            }

            #endregion Methods
        }

        #endregion Nested Types
    }
}